package tech.waterism.utils;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.BooleanUtil;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import tech.waterism.algorithm.moea.PSO.CheckUtil;
import tech.waterism.constant.DispatchConstants;
import tech.waterism.constant.StationTypeConstans;
import tech.waterism.document.DPrincipleCustomDocument;
import tech.waterism.dto.*;
import tech.waterism.entity.DZqSpillway;
import tech.waterism.enums.*;
import tech.waterism.utils.method.ResFlood;
import tech.waterism.utils.method.ResOutFlow;
import tech.waterism.utils.method.StatisticsCalculate;
import tech.waterism.utils.method.StoreFlow;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Component
@AllArgsConstructor
@Slf4j
public class ForecastDispatchUtils {

    public static boolean useDispatchRule(DPrincipleCustomDocument principleCustomDocument, String stcd) {
        for (PrincipleRecord record : principleCustomDocument.getRecords()) {
            if (record.getCalType() != null && record.getCalType().equals(CalTypeEnum.REV_OPT)) {
                return true;
            }
            if (record.getConditions() != null && record.getConditions().stream().anyMatch(
                    i -> Boolean.TRUE.equals(i.getSetStcd()) || i.getStcd() != null && !i.getStcd().equals(stcd)) ||
                    record.getControls() != null && record.getControls().stream().anyMatch(
                            i -> Boolean.TRUE.equals(i.getSetStcd()) ||
                                    i.getStcd() != null && !i.getStcd().equals(stcd)) ||
                    record.getConstrains() != null && record.getConstrains().stream()
                            .anyMatch(i -> i.getStcd() != null && !i.getStcd().equals(stcd))

            ) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return 设备全开的qout
     */
    public static Double getQoutMaxByGateOpenAll(FPIndexDto fpIndex, ForecastDataDto datum, int timeNum) {
        if (fpIndex.getSpillways() == null) {
            return Double.MAX_VALUE;
        }
        List<DSpillwaysDto> spillways =
                fpIndex.getSpillways().stream().filter(i -> i.getSpillwaytype().equals(0)).toList();
        if (spillways.isEmpty() || spillways.get(0).getZqMethods() == null ||
                spillways.get(0).getZqMethods().isEmpty()) {
            return Double.MAX_VALUE;
        }
        Integer[][] nUsedGates = setSpillWayOpen(fpIndex);
        if (DispatchConstants.DISPATCH_OUTFLOWTYPE_SEMI_GRAPHICAL.equals(fpIndex.getOutFlowType())) {
            // 半图解法
            return getQoutBySemiGraphical(fpIndex, timeNum, nUsedGates);
        }
        // 龙格库塔 （默认）
        Double v = ResFlood.rangeKuttaStep(fpIndex.getCurveHs(), spillways, fpIndex.getData().get(timeNum - 1).getV(),
                fpIndex.getData().get(timeNum).getQIn(),
                CommonFun.multiKFromFlowToVolumn(fpIndex.getClen(), fpIndex.getUnt()), nUsedGates);
        return ResFlood.waterBalanceByWaterLevel(fpIndex.getData().get(timeNum - 1).getV(), v, datum.getQIn(),
                CommonFun.multiKFromFlowToVolumn(fpIndex.getClen(), fpIndex.getUnt()));
    }

    /**
     * @return 闸门全开 的  nUsedGates
     */
    public static Integer[][] setSpillWayOpen(FPIndexDto fpIndex) {
        Integer[][] nUsedGates = new Integer[fpIndex.getSpillways().size()][];
        for (int i = 0; i < fpIndex.getSpillways().size(); i++) {
            DSpillwaysDto spillwaysDto = fpIndex.getSpillways().get(i);
            Integer[] methods = new Integer[spillwaysDto.getZqMethods().size()];
            for (int j = 0; j < spillwaysDto.getZqMethods().size(); j++) {
                methods[j] = 0;
            }
            methods[spillwaysDto.getZqMethods().size() - 1] = spillwaysDto.getNgates();
            nUsedGates[i] = methods;
        }
        return nUsedGates;
    }

    public static Double getCharacterValueByExtra(CatalogEnum catalog, ForecastDataDto datum, FPIndexDto fpIndex,
                                                  int timeNum, int timeNumOperate) {
        // 若选中出流：入流、入流峰值、前期入流峰值、维持出流（前一时段出流）、泄流能力。
        Double result = null;
        switch (catalog) {
            case INFLOW:
                result = datum.getQIn();
                break;
            case INFLOW_MAX:
                Date fymdh = fpIndex.getData().get(timeNumOperate).getDt();
                result = StatisticsCalculate.getQpeak(fpIndex.getData(), fymdh);
                break;
            case INFLOWEARLY_MAX:
                result = fpIndex.getData().subList(timeNumOperate, timeNum).stream()
                        .mapToDouble(ForecastDataDto::getQSim).max().getAsDouble();
                break;
            case QKEEPOUT:
                result = fpIndex.getData().get(timeNum - 1).getQOut();
                break;
            case SW:
                result = getQoutMaxByGateOpenAll(fpIndex, datum, timeNum);
                break;
            default:
                break;
        }
        return result;
    }

    public static List<ForecastDataDto> getEmptyList(List<Date> dates) {
        List<ForecastDataDto> list = new ArrayList<>();
        for (Date date : dates) {
            ForecastDataDto forecastDataDto = new ForecastDataDto();
            forecastDataDto.setDt(date);
            forecastDataDto.setQ(0d);
            list.add(forecastDataDto);
        }
        return list;
    }

    public static Double getQoutBySemiGraphical(FPIndexDto fpIndex, int timeNum, Integer[][] nUsedGates) {
        ForecastDataDto preData = fpIndex.getData().get(timeNum - 1);
        ForecastDataDto curData = fpIndex.getData().get(timeNum);
        int maxIter = 50;
        double Q_curr = curData.getQIn();
        double Q_prev = preData.getQIn();
        double q_low = 0;
        double q_high = Math.max(Q_curr * 3, 300);
        double q_guess = (q_low + q_high) / 2;
        double q_prev = preData.getQOut();
        double qIn = (Q_prev + Q_curr) / 2;
        //相对误差
        double tol = 0.01;
        for (int i = 0; i < maxIter; i++) {
            double qOut = (q_prev + q_guess) / 2;
            double vend = ResFlood.waterBalance(preData.getV(), qIn, qOut,
                    CommonFun.multiKFromFlowToVolumn(fpIndex.getClen(), fpIndex.getUnt()));
            if (vend < 0) {
                vend = 0;
                q_guess = ResFlood.waterBalanceByWaterLevel(fpIndex.getData().get(timeNum - 1).getV(), vend,
                        curData.getQIn(), CommonFun.multiKFromFlowToVolumn(fpIndex.getClen(), fpIndex.getUnt()));
            }
            CurveZVDto zvDto = ConvertZV.getZFromV(fpIndex.getCurveHs(), vend);
            double zend = zvDto.getResult();
            ConvertZV.setErrMsgCurveHs(fpIndex, zvDto.getMsg());
            // 理论下泄流量
            double q_cal = ResOutFlow.getOutflow(zend, nUsedGates, fpIndex.getSpillways());
            double rel_error = Math.abs(q_guess - q_cal) / Math.max(q_guess, Math.max(q_cal, 1e-5));
            if (rel_error <= tol) {
                break;
            }
            // 调整猜测值(二分法)
            if (q_guess > q_cal) {
                q_high = q_guess;
            } else {
                q_low = q_guess;
            }
            q_guess = (q_low + q_high) / 2;
        }
        return q_guess;
    }

    /**
     * 是否在计算日期内
     */
    @SneakyThrows
    public boolean inCalTime(PrincipleRecord record, Date curDate) {
        if (record.getApplicableStartDate() == null || record.getApplicableEndDate() == null) {
            throw new Exception("请填写适用日期");
        }
        SimpleDateFormat sfCh = new SimpleDateFormat("M月d日");
        SimpleDateFormat sfMd = new SimpleDateFormat("MMdd");
        String applicableStartDate = record.getApplicableStartDate();
        String applicableEndDate = record.getApplicableEndDate();
        Date start = sfCh.parse(applicableStartDate);
        Date end = sfCh.parse(applicableEndDate);
        int startInt = Integer.parseInt(sfMd.format(start));
        int endInt = Integer.parseInt(sfMd.format(end));
        int curInt = Integer.parseInt(sfMd.format(curDate));
        return curInt >= startInt && curInt <= endInt;
    }

    /**
     * 计算判断条件
     */
    @SneakyThrows
    public boolean getCondition(PrincipleCondition condition, LogFieldDto logFieldDto, FPIndexDto fpIndex, int timeNum,
                                int timeNumOperate) {
        if (BooleanUtil.isTrue(condition.getSetStcd()) &&
                (condition.getStcd() == null || condition.getStcdR() == null)) {
            throw new Exception("没有设置缺省站");
        }
        FloodCharacterDto floodCharacter = fpIndex.getFloodCharacter();
        // 当前值
        double value;
        // 上一时刻值
        double preValue;
        ForecastDataDto datum = fpIndex.getData().get(timeNum);
        ForecastDataDto preDatum = fpIndex.getData().get(timeNum - 1);
        try {
            if (condition.getTime() != null && condition.getTime().getType() != null) {
                switch (condition.getTime().getType()) {
                    case INTERVAL -> {
                        // 时段累计
                        value = getValueInterval(condition, fpIndex, timeNum, timeNumOperate);
                        preValue = getValueInterval(condition, fpIndex, timeNum - 1, timeNumOperate);
                    }
                    case WHOLE -> {
                        // 全程累计
                        value = getValueWhole(condition.getCompareField().getField(), fpIndex, timeNum, timeNumOperate);
                        preValue = getValueWhole(condition.getCompareField().getField(), fpIndex, timeNum - 1,
                                timeNumOperate);
                    }
                    case CURRENT, AFTER, BEFORE -> {
                        // 当前值
                        value = (double) CheckUtil.obj2Map(datum).get(condition.getCompareField().getField());
                        preValue = (double) CheckUtil.obj2Map(preDatum).get(condition.getCompareField().getField());
                        if (condition.getCompareField().equals(FieldEnum.RZ)) {
                            // 水位的瞬时值指上一时刻水位
                            value = preValue;
                        }
                    }
                    case STATIC -> {
                        value = getValueByStat(condition.getCompareField(), fpIndex, timeNumOperate);
                        preValue = value;
                    }
                    default -> {
                        throw new Exception("Unexpected value: " + condition.getTime().getType().getName());
                    }
                }
            } else {
                // 默认使用当前值
                value = (double) CheckUtil.obj2Map(datum).get(condition.getCompareField().getField());
                preValue = (double) CheckUtil.obj2Map(preDatum).get(condition.getCompareField().getField());
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new Exception(
                    logFieldDto.getStnm() + "(" + logFieldDto.getStcd() + ")" + "条件" + condition.getStcd() + "," +
                            condition.getCompareField().getField() + "(" + condition.getCompareField().getName() + ")" +
                            "未找到对应的值");
        }
        double lowerBound =
                condition.getLowerBound() == null ? 0 : CheckUtil.getValue(logFieldDto, condition.getLowerBound(),
                        floodCharacter, datum, fpIndex, timeNum, timeNumOperate);
        double upperBound = condition.getUpperBound() == null ? Double.MAX_VALUE : CheckUtil.getValue(logFieldDto,
                condition.getUpperBound(), floodCharacter, datum, fpIndex, timeNum, timeNumOperate);
        if (condition.getBoundLogic() == null) {
            throw new Exception("请选择范围逻辑");
        }
        boolean result;
        result = getConditionByBound(condition, value, lowerBound, upperBound);
        result = result && getConditionByTendency(condition, value, preValue);
        // 发生前 ，取反
        if (TimeTypeEnum.BEFORE.equals(condition.getTime().getType())) {
            result = !result;
        }
        return result;
    }

    @SneakyThrows
    private boolean getConditionByTendency(PrincipleCondition condition, double value, double preValue) {
        boolean result = true;
        if (condition.getTendencyEnum() != null) {
            switch (condition.getTendencyEnum()) {
                case NONE:
                    break;
                case FLAT:
                    result = value == preValue;
                    break;
                case RISE:
                    result = value > preValue;
                    break;
                case DECLINE:
                    result = value < preValue;
                    break;
                default:
                    throw new Exception("Unexpected value: " + condition.getTendencyEnum().getName());
            }
        }
        return result;
    }

    @SneakyThrows
    private boolean getConditionByBound(PrincipleCondition condition, double value, double lowerBound,
                                        double upperBound) {
        boolean result = false;
        switch (condition.getBoundLogic()) {
            case OPEN:
                if (lowerBound < value && value < upperBound) {
                    result = true;
                }
                break;
            case CLOSED:
                if (lowerBound <= value && value <= upperBound) {
                    result = true;
                }
                break;
            case FORC:
                if (lowerBound < value && value <= upperBound) {
                    result = true;
                }
                break;
            case FCRO:
                if (lowerBound <= value && value < upperBound) {
                    result = true;
                }
                break;
            default:
                throw new Exception("Unexpected value: " + condition.getBoundLogic().getName());
        }
        return result;
    }

    @SneakyThrows
    public double getValueInterval(PrincipleCondition condition, FPIndexDto fpIndex, int timeNum, int timeNumOperate) {
        Date curDate = fpIndex.getData().get(timeNum).getDt();
        double value;
        Double interval = 0d;
        switch (condition.getTime().getUnt()) {
            case D:
                interval = condition.getTime().getValue() * 24 * 60 * 60 * 1000;
                break;
            case H:
                interval = condition.getTime().getValue() * 60 * 60 * 1000;
                break;
            case M:
                interval = condition.getTime().getValue() * 60 * 1000;
                break;
            case S:
                interval = condition.getTime().getValue() * 1000;
                break;
        }
        Double finalInterval = interval;
        value = fpIndex.getData().stream()
                .filter(datum -> datum.getDt().getTime() >= curDate.getTime() - finalInterval.longValue())
                .mapToDouble(datum -> {
                    try {
                        if (Objects.nonNull(CheckUtil.obj2Map(datum).get(condition.getCompareField().getField())))
                            return (double) CheckUtil.obj2Map(datum).get(condition.getCompareField().getField());
                        else
                            return 0;
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                        return 0;
                    }
                }).sum();

        return value;
    }

    @SneakyThrows
    public double getValueWhole(String field, FPIndexDto fpIndex, int timeNum, int timeNumOperate) {
        // todo ask whole time range
        double value = 0;
        for (int i = timeNumOperate; i < fpIndex.getData().size(); i++) {
            ForecastDataDto datum = fpIndex.getData().get(i);
            value += (double) CheckUtil.obj2Map(datum).get(field);
        }
        return value;
    }

    @SneakyThrows
    public double getValueByStat(FieldEnum field, FPIndexDto fpIndex, int timeNumOperate) {
        Date fymdh = fpIndex.getData().get(timeNumOperate).getDt();
        double result = 0;
        switch (field) {
            case RCINT:
                List<StFfrarBDTO> queryListQ = new LinkedList<>();
                Double qpeak = StatisticsCalculate.getQpeak(fpIndex.getData(), fymdh);
                if (qpeak != null) {
                    StFfrarBDTO stFfrarBDTOQ =
                            new StFfrarBDTO().setQ(qpeak).setStcd(fpIndex.getStcd()).setCompareField("Q");
                    queryListQ.add(stFfrarBDTOQ);
                }
                if (queryListQ.size() > 0) {
                    ApiResult<List<StFfrarBDTO>> apiResult = informationFeignClient.getRcint(queryListQ);
                    if (apiResult.getCode() == 0) {
                        List<StFfrarBDTO> frarResult = apiResult.getData();
                        result = frarResult.get(0).getRcint();
                        break;
                    }
                } else {
                    throw new Exception("未找到rcint");
                }
            case QPEAK:
                result = fpIndex.getStatistics().getQpeak();
                break;
            case QIN_MAX:
                result = fpIndex.getData().stream().mapToDouble(ForecastDataDto::getQIn).max().getAsDouble();
                break;
            case RZ_PEAK:
                result = fpIndex.getStatistics().getRzpeak();
                break;
        }
        return result;
    }

    /**
     * 根据控制条件设置值
     */
    @SneakyThrows
    public void setValue(PrincipleRecord record, LogFieldDto logFieldDto, FPIndexDto fpIndex, int timeNum,
                         int timeNumOperate) {
        ForecastDataDto datum = fpIndex.getData().get(timeNum);
        List<PrincipleControl> controls = record.getControls();
        List<PrincipleConstraint> constrains = record.getConstrains();
        FloodCharacterDto floodCharacter = fpIndex.getFloodCharacter();
        // 控制手段
        List<FieldEnum> fields = controls.stream().map(PrincipleControl::getCompareField).toList();
        if (fields.contains(FieldEnum.QOUT)) {
            // set qout by principle
            PrincipleControl control = controls.get(fields.indexOf((FieldEnum.QOUT)));
            Object value;
            String field = control.getCompareField().getField();
            switch (control.getValueType()) {
                case FIXED:
                    value = control.getValue();
                    setValueByLogic(control.getLogic(), field, datum, value);
                    break;
                case CHARACTER:
                    // 若选中出流：入流、入流峰值、前期入流峰值、维持出流（前一时段出流）、泄流能力。
                    value = getCharacterValueByExtra(control.getCatalog(), datum, fpIndex, timeNum, timeNumOperate);
                    if (value == null) {
                        throw new Exception(
                                logFieldDto.getStnm() + "(" + logFieldDto.getStcd() + ")" + "控制项目" + field +
                                        "不存在");
                    }
                    setValueByLogic(control.getLogic(), field, datum, value);
                    break;
                default:
                    throw new Exception("约束值类型错误");
            }
            datum.setFlowWay(DispatchConstants.FLOW_WAY_BALANCE_BY_OUTFLOW);
            datum.setQOutIsExist(true);
        } else if (fields.contains(FieldEnum.SPILLWAY)) {
            List<PrincipleControl> controlsSpill =
                    controls.stream().filter(i -> i.getCompareField().equals(FieldEnum.SPILLWAY)).toList();
            ResFlood.initialGate(fpIndex, timeNum);
            for (PrincipleControl control : controlsSpill) {
                // set nUsedGates by principle
                switch (control.getValueType()) {
                    case FIXED:
                        setSpillValueByLogic(datum, fpIndex, control.getSpillwayOption(), control.getSpillwayGate(),
                                datum.getNUsedGates());
                        break;
                    case CHARACTER:
                        // todo
                        setSpillCharacterValueByLogic(logFieldDto, control.getCatalog(), floodCharacter, datum, fpIndex,
                                control.getSpillwayOption(), timeNum);
                        break;
                    default:
                        throw new Exception("约束值类型错误");
                }
            }
            datum.setFlowWay(DispatchConstants.FLOW_WAY_GATE_BY_RUNGEKUTTA);
        } else {
            datum.setQOut(fpIndex.getData().get(timeNum - 1).getQOut());
            datum.setQOutIsExist(true);
        }
        if (datum.getNUsedGates() != null && datum.getFlowWay().equals(DispatchConstants.FLOW_WAY_GATE_BY_RUNGEKUTTA)) {
            if (DispatchConstants.DISPATCH_OUTFLOWTYPE_SEMI_GRAPHICAL.equals(fpIndex.getOutFlowType())) {
                setWaterBySemiGraphical(fpIndex, datum, timeNum);
            } else {
                setWaterByRungekutta(fpIndex, datum, timeNum);
            }
        } else {
            setRzByQout(fpIndex, timeNum);
        }
        Object value;
        if (fields.contains(FieldEnum.RZ)) {
            PrincipleControl control = controls.get(fields.indexOf((FieldEnum.RZ)));
            value = control.getValue();
            setValueByLogic(control.getLogic(), FieldEnum.RZ.getField(), datum, value);
            setQoutByRz(fpIndex, timeNum);
            datum.setFlowWay(DispatchConstants.FLOW_WAY_BALANCE_BY_WATERLEVEL);
        }
        // 约束条件
        if (constrains != null) {
            FieldEnum changeType = null;
            for (PrincipleConstraint constrain : constrains) {
                value = constrain.getValue();
                if (constrain.getValueType().equals(ValueTypeEnum.CHARACTER)) {
                    if (constrain.getCompareField().equals(FieldEnum.QOUT)) {
                        value = getCharacterValueByExtra(constrain.getCatalog(), datum, fpIndex, timeNum,
                                timeNumOperate);
                        if (value == null) {
                            throw new Exception(logFieldDto.getStnm() + "(" + logFieldDto.getStcd() + ")" + "约束条件" +
                                    constrain.getCompareField() + "不存在");
                        }
                    }
                    if (constrain.getCompareField().equals(FieldEnum.RZ)) {
                        value = CheckUtil.getCharacterValue(logFieldDto, constrain.getCatalog(), floodCharacter, datum);
                        if (value == null) {
                            throw new Exception(logFieldDto.getStnm() + "(" + logFieldDto.getStcd() + ")" + "约束条件" +
                                    constrain.getCompareField() + "不存在");
                        }
                    }
                }
                boolean changed =
                        setValueByLogic(constrain.getLogic(), constrain.getCompareField().getField(), datum, value);
                if (changed) {
                    changeType = constrain.getCompareField();
                }
            }
            if (changeType != null) {
                switch (changeType) {
                    case QOUT -> setRzByQout(fpIndex, timeNum);
                    case RZ -> setQoutByRz(fpIndex, timeNum);
                }
            }
        }
        // 最大泄量
        double qCapacity = getQoutMaxByGateOpenAll(fpIndex, datum, timeNum);
        datum.setQCapacity(qCapacity);
        if (datum.getQOut() > qCapacity) {
            datum.setQOut(qCapacity);
            setRzByQout(fpIndex, timeNum);
        }
        datum.setUsePrinciple(true);
        datum.setRecordNo(record.getRecordNo());
    }

    public void setQoutByRz(FPIndexDto fpIndex, int timeNum) {
        ForecastDataDto datum = fpIndex.getData().get(timeNum);
        CurveZVDto zvDto1 = ConvertZV.getVFromZ(fpIndex.getCurveHs(), datum.getRz());
        datum.setV(zvDto1.getResult());
        datum.setQOut(ResFlood.waterBalanceByWaterLevel(fpIndex.getData().get(timeNum - 1).getV(), datum.getV(),
                datum.getQIn(), CommonFun.multiKFromFlowToVolumn(fpIndex.getClen(), fpIndex.getUnt())));
    }

    public void setRzByQout(FPIndexDto fpIndex, int timeNum) {
        ForecastDataDto datum = fpIndex.getData().get(timeNum);
        datum.setV(ResFlood.waterBalance(fpIndex.getData().get(timeNum - 1).getV(), datum.getQIn(), datum.getQOut(),
                CommonFun.multiKFromFlowToVolumn(fpIndex.getClen(), fpIndex.getUnt())));
        if (datum.getV() < 0) {
            datum.setV(0d);
            datum.setQOut(ResFlood.waterBalanceByWaterLevel(fpIndex.getData().get(timeNum - 1).getV(), datum.getV(),
                    datum.getQIn(), CommonFun.multiKFromFlowToVolumn(fpIndex.getClen(), fpIndex.getUnt())));
        }
        CurveZVDto zvDto = ConvertZV.getZFromV(fpIndex.getCurveHs(), datum.getV());
        datum.setRz(zvDto.getResult());
        ConvertZV.setErrMsgCurveHs(fpIndex, zvDto.getMsg());
    }

    public void setRzByQoutSemiGraphical(FPIndexDto fpIndex, int timeNum) {
        ForecastDataDto datum = fpIndex.getData().get(timeNum);
        ForecastDataDto foreData = fpIndex.getData().get(timeNum - 1);
        double v = ResFlood.waterBalance(foreData.getV(), (datum.getQIn() + foreData.getQIn()) / 2,
                (datum.getQOut() + foreData.getQOut()) / 2,
                CommonFun.multiKFromFlowToVolumn(fpIndex.getClen(), fpIndex.getUnt()));
        datum.setV(v);
        if (datum.getV() < 0) {
            datum.setV(0d);
            double qoutAvg = ResFlood.waterBalanceByWaterLevel(foreData.getV(), datum.getV(),
                    (datum.getQIn() + foreData.getQIn()) / 2,
                    CommonFun.multiKFromFlowToVolumn(fpIndex.getClen(), fpIndex.getUnt()));
            datum.setQOut(qoutAvg * 2 - foreData.getQOut());
        }
        CurveZVDto zvDto = ConvertZV.getZFromV(fpIndex.getCurveHs(), datum.getV());
        datum.setRz(zvDto.getResult());
        ConvertZV.setErrMsgCurveHs(fpIndex, zvDto.getMsg());
    }

    public void setWaterBySemiGraphical(FPIndexDto fpIndex, ForecastDataDto datum, int timeNum) {
        double qout = getQoutBySemiGraphical(fpIndex, timeNum, datum.getNUsedGates());
        datum.setQOut(qout);
        setRzByQoutSemiGraphical(fpIndex, timeNum);
    }

    public void setWaterByRungekutta(FPIndexDto fpIndex, ForecastDataDto datum, int timeNum) {
        Double rzStart = fpIndex.getData().get(timeNum - 1).getRz();
        datum.setV(ResFlood.rangeKuttaToV(fpIndex, timeNum));
        CurveZVDto zvDto = ConvertZV.getZFromV(fpIndex.getCurveHs(), datum.getV());
        double curRz = zvDto.getResult();
        Double zopen = fpIndex.getSpillways().stream().map(i -> i.getZopen()).min(Double::compare).get();
        if (curRz < zopen && rzStart >= zopen) {
            curRz = zopen;
            CurveZVDto zvDtoNew = ConvertZV.getVFromZ(fpIndex.getCurveHs(), curRz);
            datum.setV(zvDtoNew.getResult());
        }
        datum.setRz(curRz);
        ConvertZV.setErrMsgCurveHs(fpIndex, zvDto.getMsg());
        double qout_new = ResFlood.waterBalanceByWaterLevel(fpIndex.getData().get(timeNum - 1).getV(),
                fpIndex.getData().get(timeNum).getV(), fpIndex.getData().get(timeNum).getQIn(),
                CommonFun.multiKFromFlowToVolumn(fpIndex.getClen(), fpIndex.getUnt()));
        if (Math.abs(qout_new) < 0.00001) {
            qout_new = 0d;
        }
        if (curRz < zopen && rzStart < zopen && qout_new > 0) {
            qout_new = 0;
            datum.setV(ResFlood.waterBalance(fpIndex.getData().get(timeNum - 1).getV(), datum.getQIn(), qout_new,
                    CommonFun.multiKFromFlowToVolumn(fpIndex.getClen(), fpIndex.getUnt())));
        }
        datum.setQOut(qout_new);
    }

    /**
     * 空白逻辑，未实现
     */
    public void setSpillCharacterValueByLogic(LogFieldDto logFieldDto, CatalogEnum catalog,
                                              FloodCharacterDto floodCharacter, ForecastDataDto datum, FPIndexDto fpIndex, Integer spillwayOption,
                                              Integer timeNum) {
        // 若选中设备：全部开启、部分开启
        // todo
    }

    @SneakyThrows
    public void setSpillValueByLogic(ForecastDataDto datum, FPIndexDto fpIndex, Integer spillwayOption,
                                     Integer[] spillwayGate, Integer[][] nUsedGates) {
        List<DSpillwaysDto> spillways = fpIndex.getSpillways();
        for (int i = 0; i < spillways.size(); i++) {
            DSpillwaysDto spillway = spillways.get(i);
            if (spillway.getSpillwayid().equals(spillwayOption)) {
                if (spillwayGate != null) {
                    nUsedGates[i] = spillwayGate;
                }
            }
        }
        datum.setNUsedGates(nUsedGates);
        // todo  test
        String mannerNote = "";
        for (int j = 0; j < spillways.size(); j++) {
            for (int k = 0; k < spillways.get(j).getNopenstates(); k++) {
                if (datum.getNUsedGates()[j][k] > 0) {
                    mannerNote = mannerNote + spillways.get(j).getShortname() + " " +
                            spillways.get(j).getZqMethods().get(k).getStatename() + "-" + datum.getNUsedGates()[j][k] +
                            ";";
                }
            }
        }
        datum.setGateNote(mannerNote);
    }

    /**
     * 根据逻辑设置值，如果当前值不满足逻辑，则设置为新值
     */
    public boolean setValueByLogic(LogicEnum logic, String field, ForecastDataDto datum, Object compareValue)
            throws Exception {
        boolean changed = false;
        double currentValue = 0;
        try {
            currentValue = (double) CheckUtil.obj2Map(datum).get(field);
        } catch (Exception e) {
            log.error("字段" + field + "不存在");
        }
        switch (logic) {
            case GTE:
                if (!(currentValue >= (double) compareValue)) {
                    CheckUtil.setData(datum, field, compareValue);
                    changed = true;
                }
                break;
            case GT:
                if (!(currentValue > (double) compareValue)) {
                    CheckUtil.setData(datum, field, compareValue);
                    changed = true;
                }
                break;
            case EQ:
            case AEQ:
                CheckUtil.setData(datum, field, compareValue);
                changed = true;
                break;
            case LT:
                if (!(currentValue < (double) compareValue)) {
                    CheckUtil.setData(datum, field, compareValue);
                    changed = true;
                }
                break;
            case LTE:
                if (!(currentValue <= (double) compareValue)) {
                    CheckUtil.setData(datum, field, compareValue);
                    changed = true;
                }
                break;
            default:
                throw new Exception("Unexpected value: " + logic.getName());
        }
        return changed;
    }

    public void normalWaterbalance(FPIndexDto fpIndex, ForecastDataDto datum, int timeNum) {
        ForecastDataDto datumPre = fpIndex.getData().get(timeNum - 1);
        switch (datumPre.getFlowWay()) {
            case DispatchConstants.FLOW_WAY_GATE_BY_RUNGEKUTTA:
                datum.setNUsedGates(datumPre.getNUsedGates());
                if (DispatchConstants.DISPATCH_OUTFLOWTYPE_SEMI_GRAPHICAL.equals(fpIndex.getOutFlowType())) {
                    setWaterBySemiGraphical(fpIndex, datum, timeNum);
                } else {
                    setWaterByRungekutta(fpIndex, datum, timeNum);
                }
                break;
            case DispatchConstants.FLOW_WAY_BALANCE_BY_OUTFLOW:
                double qCapacity = ForecastDispatchUtils.getQoutMaxByGateOpenAll(fpIndex, datum, timeNum);
                double qout = datum.getQOut();
                if (datum.getQOutIsExist() == null || !datum.getQOutIsExist()) {
                    qout = datumPre.getQOut();
                }
                if (qout > qCapacity) {
                    qout = qCapacity;
                }
                datum.setQOut(qout);
                setRzByQout(fpIndex, timeNum);
                break;
            default:
                log.info("使用其他flowway");
                break;
        }
        datum.setFlowWay(datumPre.getFlowWay());
        datum.setUsePrinciple(false);
    }

    /**
     * 枢纽分流计算
     */
    @SneakyThrows
    public void setDisValue(FPIndexDto fpIndex, int timeNum, PrincipleRecord record,
                            Map<String, FloodCharacterDto> characterDtoMap) {
        Map<String, List<ForecastDataDto>> hubDisData = fpIndex.getHubDisData();
        ForecastDataDto datum = fpIndex.getData().get(timeNum);
        Map<String, Double> temp = new HashMap<>();
        record.getControls().sort((o1, o2) -> {
            // return o1 - o2;
            if (o1.getValueType().equals(o2.getValueType())) {
                return 0;
            }
            if (o1.getValueType().equals(ValueTypeEnum.CALCULATE)) {
                return 1;
            } else if (o1.getValueType().equals(ValueTypeEnum.FIXED)) {
                return -1;
            } else if (o2.getValueType().equals(ValueTypeEnum.CALCULATE)) {
                return -1;
            } else if (o2.getValueType().equals(ValueTypeEnum.FIXED)) {
                return 1;
            }
            return 1;
        });
        for (PrincipleControl control : record.getControls()) {
            Double value = null;
            switch (control.getValueType()) {
                case FIXED -> {
                    value = control.getValue();
                }
                case CHARACTER -> {
                    // todo 需要全部的characterMap
                    //  characterDtoMap
                    switch (control.getCatalog()) {
                        case INFLOW -> {
                            value = fpIndex.getData().get(timeNum).getQIn();
                        }
                        default -> {
                            throw new Exception("尚未实现，请联系管理员");
                        }
                    }
                }
                case CALCULATE -> {
                    Double qin = fpIndex.getData().get(timeNum).getQIn();
                    value = qin;
                    double sub = 0;
                    for (StationCharInfoDto stationCharInfoDto : control.getCalRelation()) {
                        if (stationCharInfoDto.getStcd() != null) {
                            if (stationCharInfoDto.getStcd().contains("SN")) {
                                // todo 没想好枢纽要不要加到条件里，先跳过这个，目前枢纽只有入流跟闸门出流
                                continue;
                            }
                            sub = temp.get(stationCharInfoDto.getStcd());
                        } else {
                            sub = stationCharInfoDto.getValue();
                        }
                        switch (stationCharInfoDto.getOperator()) {
                            case "-" -> {
                                value = value - sub;
                            }
                            case "*" -> {
                                value = value * sub;
                            }
                            default -> {
                                throw new Exception("尚未实现，请联系管理员");
                            }
                        }
                    }
                }
            }
            temp.put(control.getStcd(), value);
            hubDisData.get(control.getStcd()).get(timeNum).setQ(value);
        }
        datum.setUsePrinciple(true);
        datum.setRecordNo(record.getRecordNo());
    }

    @SneakyThrows
    public boolean getCombCondition(TopoCalDto topoCalDto, PrincipleCondition condition, int timeNum,
                                    int timeNumOperate) {
        Map<String, FPIndexDto> dataMap = topoCalDto.getIndices().stream()
                .collect(Collectors.toMap(i -> i.getFpIndex().getStcd(), TopoIndexDto::getFpIndex, (k1, k2) -> k1));
        if (!dataMap.containsKey(condition.getStcd())) {
            throw new Exception("站点不在结构中，请检查：" + condition.getStcd());
        }
        // 当前值
        double value = getValueByTimeType(condition, dataMap.get(condition.getStcd()), timeNum, timeNumOperate);
        if (condition.getCombConditions() != null && !condition.getCombConditions().isEmpty()) {
            for (StationCharInfoDto compCondition : condition.getCombConditions()) {
                PrincipleCondition conditionComp = new PrincipleCondition();
                conditionComp.setStcd(compCondition.getStcd());
                conditionComp.setCompareField(compCondition.getField());
                double subValue = getValueByTimeType(conditionComp, dataMap.get(conditionComp.getStcd()), timeNum,
                        timeNumOperate);
                switch (compCondition.getOperator()) {
                    case "+" -> {
                        value += subValue;
                    }
                    case "-" -> {
                        value -= subValue;
                    }
                    case "*" -> {
                        value *= subValue;
                    }
                    default -> {
                        throw new Exception("Unexpected value: " + compCondition.getOperator());
                    }
                }
            }
        }
        if (condition.getLowerBound() != null &&
                condition.getLowerBound().getValueType().equals(ValueTypeEnum.CHARACTER) ||
                condition.getUpperBound() != null &&
                        condition.getUpperBound().getValueType().equals(ValueTypeEnum.CHARACTER)) {
            throw new Exception("组合条件不能使用特征值");
        }
        double lowerBound =
                condition.getLowerBound() == null ? 0 : CheckUtil.getValue(null, condition.getLowerBound(), null, null);
        double upperBound = condition.getUpperBound() == null ? Double.MAX_VALUE : CheckUtil.getValue(null,
                condition.getUpperBound(), null, null);
        if (condition.getBoundLogic() == null) {
            throw new Exception("请选择范围逻辑");
        }
        boolean result = getConditionByBound(condition, value, lowerBound, upperBound);
        double preValue = getValueByTimeType(condition, dataMap.get(condition.getStcd()), timeNum - 1, timeNumOperate);
        result = result && getConditionByTendency(condition, value, preValue);
        return result;
    }

    @SneakyThrows
    private double getValueByTimeType(PrincipleCondition condition, FPIndexDto fpIndex, int timeNum,
                                      int timeNumOperate) {
        try {
            double value;
            ForecastDataDto datum = fpIndex.getData().get(timeNum);
            ForecastDataDto preDatum = fpIndex.getData().get(timeNum - 1);
            if (condition.getTime() != null && condition.getTime().getType() != null) {
                switch (condition.getTime().getType()) {
                    case INTERVAL -> {
                        // 时段累计
                        value = getValueInterval(condition, fpIndex, timeNum, timeNumOperate);
                    }
                    case WHOLE -> {
                        // 全程累计
                        value = getValueWhole(condition.getCompareField().getField(), fpIndex, timeNum, timeNumOperate);
                    }
                    case CURRENT, AFTER, BEFORE -> {
                        // 当前值
                        value = (double) CheckUtil.obj2Map(datum).get(condition.getCompareField().getField());
                        double preValue =
                                (double) CheckUtil.obj2Map(preDatum).get(condition.getCompareField().getField());
                        if (condition.getCompareField().equals(FieldEnum.RZ)) {
                            // 水位的瞬时值指上一时刻水位
                            value = preValue;
                        }
                    }
                    case STATIC -> {
                        value = getValueByStat(condition.getCompareField(), fpIndex, timeNumOperate);
                    }
                    default -> {
                        throw new Exception("Unexpected value: " + condition.getTime().getType().getName());
                    }
                }
            } else {
                // 默认使用当前值
                value = (double) CheckUtil.obj2Map(datum).get(condition.getCompareField().getField());
            }
            return value;
        } catch (Exception e) {
            throw new Exception(fpIndex.getStnm() + "(" + fpIndex.getStcd() + ")" + "条件" + condition.getStcd() + "," +
                    condition.getCompareField().getField() + "(" + condition.getCompareField().getName() + ")" +
                    "未找到对应的值");
        }
    }

    /**
     * 分洪计算
     */
    @SneakyThrows
    public void setDvrValue(PrincipleRecord record, TopoCalDto topoCalDto, FPIndexDto fpIndex, int timeNum) {
        List<PrincipleControl> controls = record.getControls();
        // todo 目前 分洪只有一种控制方式(分洪流量或者闸门)
        List<PrincipleControl> controlsDvr =
                controls.stream().filter(i -> i.getCompareField().equals(FieldEnum.QDVR)).toList();
        List<PrincipleControl> controlsSpill =
                controls.stream().filter(i -> i.getCompareField().equals(FieldEnum.SPILLWAY)).toList();
        List<PrincipleConstraint> constrains = record.getConstrains();
        Map<String, FPIndexDto> dataMap = topoCalDto.getIndices().stream()
                .collect(Collectors.toMap(i -> i.getFpIndex().getStcd(), TopoIndexDto::getFpIndex, (k1, k2) -> k1));
        // 计算值
        double value = 0;
        Map<String, List<ForecastDataDto>> hubDisData = fpIndex.getHubDisData();
        // 是否要执行赋值
        boolean executeFlg = false;
        double qoutOri = 0;
        // todo 目前 只有一个分洪流量控制
        for (PrincipleControl control : controlsDvr) {
            // 分洪排洪计算数值
            value = getValueByCalRelation(control, dataMap, timeNum);
            if (control.getLogic() != null) {
                qoutOri = hubDisData.get(control.getStcd()).get(timeNum).getQ();
                executeFlg = CheckUtil.checkLogic(value, qoutOri, control.getLogic());
                if (executeFlg) {
                    value = qoutOri;
                }
            }
        }
        // 分洪
        // 根据设施dtcd, 查找下游index(蓄滞洪区站码)
        String dtcd = controls.get(0).getStcd();
        FPIndexDto fpIndexDvr = findDownDvrIndex(dtcd, topoCalDto);
        List<ForecastDataDto> dataDvr = fpIndexDvr.getData();
        ForecastDataDto datumDvr = dataDvr.get(timeNum);
        List<DSpillwaysDto> spillwaysDvr = fpIndexDvr.getSpillways();
        // 分洪设备曲线
        DSpillwaysDto spillWayInDvr =
                spillwaysDvr.stream().filter(i -> i.getSpillwaytype() == 3 || i.getSpillwaytype() == 1).toList().get(0);
        List<DZqSpillway> zqSpillways = spillWayInDvr.getZqMethods().get(0).getZqSpillways();
        Integer spillwayidIndex = getSpillwayIndex(spillWayInDvr, spillwaysDvr);
        // 分洪 river -> store
        double zRiver = MathFun.insert3(value, zqSpillways.stream().mapToDouble(DZqSpillway::getQ).toArray(),
                zqSpillways.stream().mapToDouble(DZqSpillway::getZ).toArray(), zqSpillways.size());
        double zStore = dataDvr.get(timeNum - 1).getRz();
        double qout;
        if (zRiver <= zStore) {
            return;
        } else {
            qout = value;
            double gateWidth = StoreFlow.getGateWidth(zRiver, zStore, qout, spillWayInDvr, fpIndexDvr.getHsh0s());
            qout = setGateWidthByConstraint(constrains, gateWidth, value, zRiver, zStore, spillWayInDvr, fpIndexDvr,
                    datumDvr, spillwayidIndex);
        }
        if (constrains != null && !constrains.isEmpty()) {
            for (PrincipleConstraint constrain : constrains) {
                if (constrain.getCompareField().equals(FieldEnum.SPILLWAY) && constrain.getCatalog() != null &&
                        constrain.getCatalog().equals(CatalogEnum.GATE_KEEP)) {
                    // 分洪阶段, 口门不可减少（使用约束实现）
                    if (datumDvr.getNUsedGates()[spillwayidIndex][0] <
                            fpIndexDvr.getData().get(timeNum - 1).getNUsedGates()[spillwayidIndex][0]) {
                        datumDvr.getNUsedGates()[spillwayidIndex][0] =
                                fpIndexDvr.getData().get(timeNum - 1).getNUsedGates()[spillwayidIndex][0];
                        qout = StoreFlow.outflowFromOneSpillway(zRiver, zStore, spillWayInDvr,
                                datumDvr.getNUsedGates()[spillwayidIndex][0], fpIndexDvr.getHsh0s());
                    }
                }
            }
        }

        if (!controlsSpill.isEmpty()) {
            // 通过闸门宽度控制出流
            // todo 目前只有一个分洪闸口
            PrincipleControl control = controlsSpill.get(0);
            int valueGate = 0;
            for (StationCharInfoDto stationCharInfoDto : control.getCalRelation()) {
                if (stationCharInfoDto.getUnit() != null) {
                    DateUnit unit;
                    if (stationCharInfoDto.getUnit().equals("M")) {
                        unit = DateUnit.MINUTE;
                    } else {
                        unit = DateUnit.HOUR;
                    }
                    long btw = DateUtil.between(dataDvr.get(timeNum - 1).getDt(), datumDvr.getDt(), unit);
                    valueGate = (int) Math.ceil(stationCharInfoDto.getValue() * btw);
                } else {
                    valueGate = control.getCalRelation().get(0).getValue().intValue();
                }
            }
            int nUsedGates1 = dataDvr.get(timeNum - 1).getNUsedGates()[spillwayidIndex][0] - valueGate;
            if (nUsedGates1 < 0) {
                nUsedGates1 = 0;
            }
            datumDvr.getNUsedGates()[spillwayidIndex][0] = nUsedGates1;
            qout = StoreFlow.outflowFromOneSpillway(zRiver, zStore, spillWayInDvr,
                    datumDvr.getNUsedGates()[spillwayidIndex][0], fpIndexDvr.getHsh0s());
        }
        // 重新分洪
        String disStcd =
                fpIndex.getHub().getGate().stream().filter(item -> item.getGttp().equals(DispatchConstants.DIS))
                        .toList().get(0).getDtcd();
        updateDisData(hubDisData, qout, dtcd, timeNum, disStcd, fpIndex);
        fpIndexDvr.getData().get(timeNum).setIGrade(1);
        fpIndex.getData().get(timeNum).setUsePrinciple(true);
        fpIndex.getData().get(timeNum).setRecordNo(record.getRecordNo());
    }

    private void updateDisData(Map<String, List<ForecastDataDto>> hubDisData, double qout, String dtcd, int timeNum,
                               String disStcd, FPIndexDto fpIndex) {
        if (qout > 0) {
            for (Map.Entry<String, List<ForecastDataDto>> entry : hubDisData.entrySet()) {
                if (entry.getKey().equals(dtcd)) {
                    // 分洪
                    entry.getValue().get(timeNum).setQ(qout);
                } else if (entry.getKey().equals(disStcd)) {
                    // 行洪
                    entry.getValue().get(timeNum).setQ(fpIndex.getData().get(timeNum).getQSim() - qout);
                } else {
                    // 清空
                    entry.getValue().get(timeNum).setQ(0.0);
                }
            }
        }
    }

    /**
     * 排洪计算
     */
    @SneakyThrows
    public void setDscValue(PrincipleRecord record, TopoCalDto topoCalDto, FPIndexDto fpIndex, int timeNum) {
        List<PrincipleControl> controls = record.getControls();
        List<PrincipleConstraint> constrains = record.getConstrains();
        Map<String, FPIndexDto> dataMap = topoCalDto.getIndices().stream()
                .collect(Collectors.toMap(i -> i.getFpIndex().getStcd(), TopoIndexDto::getFpIndex, (k1, k2) -> k1));
        // 蓄滞洪区数据
        List<ForecastDataDto> data = fpIndex.getData();
        ForecastDataDto datum = fpIndex.getData().get(timeNum);
        List<DSpillwaysDto> spillways = fpIndex.getSpillways();
        // 排洪设备曲线
        DSpillwaysDto spillWayInDsc =
                spillways.stream().filter(i -> i.getSpillwaytype() == 3 || i.getSpillwaytype() == 2).toList().get(0);
        List<DZqSpillway> zqSpillways = spillWayInDsc.getZqMethods().get(0).getZqSpillways();
        Integer spillwayidIndex = getSpillwayIndex(spillWayInDsc, spillways);
        double value = 0;
        boolean executeFlg = false;
        Double qoutOri = null;
        // todo 当前排洪只用 排洪流量控制
        for (PrincipleControl control : controls) {
            value = getValueByCalRelation(control, dataMap, timeNum);
            if (value < 0) {
                value = 0;
            }
            if (control.getLogic() != null && control.getCompareField().equals(FieldEnum.QDSC)) {
                if (qoutOri == null) {
                    qoutOri = value;
                } else {
                    executeFlg = !CheckUtil.checkLogic(value, qoutOri, control.getLogic());
                    if (executeFlg) {
                        qoutOri = value;
                    }
                }
            }
        }
        if (qoutOri != null) {
            value = qoutOri;
        }
        // 排洪 store -> river
        double zRiver = MathFun.insert3(value, zqSpillways.stream().mapToDouble(DZqSpillway::getQ).toArray(),
                zqSpillways.stream().mapToDouble(DZqSpillway::getZ).toArray(), zqSpillways.size());
        double zStore = data.get(timeNum - 1).getRz();
        double qout;
        if (zStore <= zRiver) {
            if (constrains != null && !constrains.isEmpty() && constrains.stream().anyMatch(
                    constrain -> constrain.getCompareField().equals(FieldEnum.SPILLWAY) &&
                            constrain.getCatalog() != null && constrain.getCatalog().equals(CatalogEnum.GATE_KEEP)) &&
                    data.get(timeNum - 1).getNUsedGates()[spillwayidIndex][0] > 0) {
                // 如果开始排洪了，就需要继续
                qout = 0;
            } else {
                return;
            }
        } else {
            qout = value;
            double gateWidth = StoreFlow.getGateWidth(zStore, zRiver, qout, spillWayInDsc, fpIndex.getHsh0s());
            if (gateWidth < 0) {
                gateWidth = 0;
            }
            qout = setGateWidthByConstraint(constrains, gateWidth, value, zStore, zRiver, spillWayInDsc, fpIndex, datum,
                    spillwayidIndex);
        }
        if (constrains != null && !constrains.isEmpty()) {
            for (PrincipleConstraint constrain : constrains) {
                if (constrain.getCompareField().equals(FieldEnum.SPILLWAY) && constrain.getCatalog() != null &&
                        constrain.getCatalog().equals(CatalogEnum.GATE_KEEP)) {
                    // 排洪阶段, 口门不可减少（使用约束实现）
                    if (datum.getNUsedGates()[spillwayidIndex][0] <
                            data.get(timeNum - 1).getNUsedGates()[spillwayidIndex][0]) {
                        datum.getNUsedGates()[spillwayidIndex][0] =
                                data.get(timeNum - 1).getNUsedGates()[spillwayidIndex][0];
                        qout = StoreFlow.outflowFromOneSpillway(zStore, zRiver, spillWayInDsc,
                                datum.getNUsedGates()[spillwayidIndex][0], fpIndex.getHsh0s());
                    }
                }
            }
        }
        if (qout < 0) {
            qout = 0;
        }
        datum.setQOut(qout);
        setRzByQout(fpIndex, timeNum);
        fpIndex.getData().get(timeNum).setIGrade(2);
        datum.setUsePrinciple(true);
        datum.setRecordNo(record.getRecordNo());
    }

    private Integer getSpillwayIndex(DSpillwaysDto spillWayInDsc, List<DSpillwaysDto> spillways) {
        // todo 使用规则指定的 spillwayOption == spillwayid ？？ 暂时写死
        Integer spillwayid = spillWayInDsc.getSpillwayid();
        int spillwayidIndex = 0;
        for (int i = 0; i < spillways.size(); i++) {
            DSpillwaysDto spillway = spillways.get(i);
            if (spillway.getSpillwayid().equals(spillwayid)) {
                spillwayidIndex = i;
            }
        }
        return spillwayidIndex;
    }

    @SneakyThrows
    private double getValueByCalRelation(PrincipleControl control, Map<String, FPIndexDto> dataMap, int timeNum) {
        double value = 0;
        switch (control.getValueType()) {
            case CALCULATE -> {
                for (StationCharInfoDto stationCharInfoDto : control.getCalRelation()) {
                    double subValue = 0;
                    if (stationCharInfoDto.getValue() != null) {
                        subValue = stationCharInfoDto.getValue();
                    } else if (stationCharInfoDto.getStcd() != null) {
                        if (!dataMap.containsKey(stationCharInfoDto.getStcd())) {
                            throw new Exception("站点不在结构中，请检查：" + stationCharInfoDto.getStcd());
                        }
                        ForecastDataDto datum = dataMap.get(stationCharInfoDto.getStcd()).getData().get(timeNum);
                        subValue = (double) CheckUtil.obj2Map(datum).get(stationCharInfoDto.getField().getField());
                    }
                    switch (stationCharInfoDto.getOperator()) {
                        case "+" -> {
                            value += subValue;
                        }
                        case "-" -> {
                            value -= subValue;
                        }
                        case "*" -> {
                            value *= subValue;
                        }
                        default -> {
                            throw new Exception("Unexpected value: " + stationCharInfoDto.getOperator());
                        }
                    }
                }
            }
            case FIXED -> {
                value = control.getValue();
            }
            default -> {
                throw new Exception("尚未实现，请联系管理员");
            }
        }
        return value;
    }

    /**
     * @param zUp   上游水位
     * @param zDown 下游水位
     */
    private double setGateWidthByConstraint(List<PrincipleConstraint> constrains, double gateWidth, double value,
                                            double zUp, double zDown, DSpillwaysDto spillWay, FPIndexDto fpIndex, ForecastDataDto datum,
                                            Integer spillwayidIndex) {
        double qout = value;
        if (constrains != null && !constrains.isEmpty()) {
            for (PrincipleConstraint constrain : constrains) {
                if (constrain.getCompareField().equals(FieldEnum.SPILLWAY) &&
                        constrain.getValueType().equals(ValueTypeEnum.CEIL)) {
                    // 向上取整（使用约束实现）
                    datum.getNUsedGates()[spillwayidIndex][0] = (int) Math.ceil(gateWidth);
                    qout = StoreFlow.outflowFromOneSpillway(zUp, zDown, spillWay,
                            datum.getNUsedGates()[spillwayidIndex][0], fpIndex.getHsh0s());
                }
            }
        } else {
            // 默认用公式约束
            if (gateWidth > spillWay.getNgates() * spillWay.getZqMethods().get(0).getGatewidth()) {
                datum.getNUsedGates()[spillwayidIndex][0] = spillWay.getNgates();
                qout = StoreFlow.getOutFlow(zUp, datum.getNUsedGates()[spillwayidIndex][0], fpIndex.getSpillways());
            } else {
                datum.getNUsedGates()[spillwayidIndex][0] =
                        (int) Math.ceil(gateWidth / spillWay.getZqMethods().get(0).getGatewidth());
            }
        }
        return qout;
    }

    /**
     * 查找下游蓄滞洪区节点
     */
    @SneakyThrows
    private FPIndexDto findDownDvrIndex(String dtcd, TopoCalDto topoCalDto) {
        FPIndexDto fpIndexDvr;
        // 分洪绑定在上游

        for (TopoIndexDto index : topoCalDto.getIndices()) {
            FPInputDto inputDto =
                    index.getFpInputs().stream().filter(i -> i.getArcd().equals(dtcd)).findFirst().orElse(null);
            if (inputDto != null) {
                fpIndexDvr = index.getFpIndex();
                return fpIndexDvr;
            }
        }
        throw new Exception("分洪站点不在河系中");
    }
}
